package com.ljg.activiti.b_processdefinition;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipInputStream;

import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.ProcessDefinition;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.FileCopyUtils;

/**
 * 流程定义相关测试类
 * @author guo
 *
 */
public class ProcessDefinitionTest {

	private Logger log = LoggerFactory.getLogger(ProcessDefinitionTest.class);
	
	// 流程引擎对象
	private ProcessEngine processEngine;
	
	@Before
	public void before() {
		/**
		 * 创建默认的流程引擎对象，默认读取classpath下的activiti.cfg.xml创建流程引擎对象
		 */
		processEngine = ProcessEngines.getDefaultProcessEngine();
		log.info("创建流程引擎对象成功！");
	}

	/**
	 * 部署流程定义-从classpath
	 * 部署完成之后，数据库表的变化
	 * 	-> `act_ge_bytearray`资源表中插入数据来保存流程的bpmn文件和png文件
	 *  -> `act_ge_property`配置表中会更新数据，下次insert数据的Id
	 *  -> `act_re_deployment`流程部署表中会插入数据，流程部署的ID，名称，部署时间等
	 *  -> `act_re_procdef`流程定义表中会插入数据，流程定义Id，流程定义Key，流程定义的名称，流程定义的版本，部署Id和资源文件名称等信息
	 *  
	 *  processEngine.getRepositoryService()：获取流程定义和流程部署相关的Service
	 */
	@Test
	public void deploymentProcessDefinition_classpath() {
		Deployment deployment = processEngine.getRepositoryService() // 获得与流程定义和部署相关的Service
				.createDeployment() // 创建部署对象
				.addClasspathResource("diagrams/LeaveProcess.bpmn") // 添加资源文件
				.addClasspathResource("diagrams/LeaveProcess.png") // 添加资源文件
				.name("流程定义测试-请假流程") // 设置部署名称
				.deploy(); // 完成流程部署
		log.info("部署完成，deploymentId=" + deployment.getId() + "，deploymentName=" + deployment.getName() + "，deploymentTime=" + deployment.getDeploymentTime());
	}
	
	/**
	 * 部署流程定义-从zip
	 * 部署完成之后，数据库表的变化
	 * 	-> `act_ge_bytearray`资源表中插入数据来保存流程的bpmn文件和png文件
	 *  -> `act_ge_property`配置表中会更新数据，下次insert数据的Id
	 *  -> `act_re_deployment`流程部署表中会插入数据，流程部署的ID，名称，部署时间等
	 *  -> `act_re_procdef`流程定义表中会插入数据，流程定义Id，流程定义Key，流程定义的名称，流程定义的版本，部署Id和资源文件名称等信息
	 *  
	 *  processEngine.getRepositoryService()：获取流程定义和流程部署相关的Service
	 */
	@Test
	public void deploymentProcessDefinition_zip() {
		InputStream in = this.getClass().getClassLoader().getResourceAsStream("diagrams/LeaveProcess.zip");
		ZipInputStream zipInputStream = new ZipInputStream(in);
		Deployment deployment = processEngine.getRepositoryService() // 获得与流程定义和部署相关的Service
				.createDeployment() // 创建部署对象
				.addZipInputStream(zipInputStream) // 从zip文件加载
				.name("流程定义测试zip-请假流程") // 设置部署名称
				.deploy(); // 完成流程部署
		log.info("部署完成，deploymentId=" + deployment.getId() + "，deploymentName=" + deployment.getName() + "，deploymentTime=" + deployment.getDeploymentTime());
	}
	
	/**
	 * 查询流程定义
	 * 
	 * processEngine.getRepositoryService()：获取流程定义和流程部署相关的Service
	 */
	@Test
	public void findProcessDefinition() {
		List<ProcessDefinition> pdList = processEngine.getRepositoryService() // 获取流程定义和流程部署相关的Service
			.createProcessDefinitionQuery() // 创建流程定义查询 
			/* 指定查询条件 */
//			.deploymentId(deploymentId) // 使用部署对象Id查询
//			.processDefinitionId(processDefinitionId) // 使用流程定义Id查询
//			.processDefinitionKey(processDefinitionKey) // 使用流程定义的key查询
//			.processDefinitionNameLike(processDefinitionNameLike) // 使用流程定义的名称模糊查询
//			.singleResult() // 返回唯一结果集
//			.count() // 返回结果集数量
//			.listPage(firstResult, maxResults) // 分页查询 
			/* 排序条件 */
//			.orderByDeploymentId().desc() // 根据部署对象Id排序 降序排列
			.orderByProcessDefinitionVersion().asc() // 按照版本升序排列
			.list(); // 返回一个集合 封装流程定义
		
		if(null == pdList || pdList.size() == 0) {
			log.error("未查询到流程定义！");
			return;
		}
		
		for(ProcessDefinition pd : pdList) {
			log.info("流程定义ID=" + pd.getId() + "，流程定义的名称=" + pd.getName() + "，流程定义的key=" + pd.getKey() + "，流程定义的版本=" + pd.getVersion() 
				+ "，流程定义的bpmn文件=" + pd.getResourceName() + "，流程定义的png文件=" + pd.getDiagramResourceName() + "，部署对象ID=" + pd.getDeploymentId());
		}
		
	}
	
	/**
	 * 删除流程定义
	 */
	@Test
	public void deleteProcessDefinition() {
		String deploymentId = "17505";
		processEngine.getRepositoryService() // 获取流程定义和流程部署相关的Service
//			.deleteDeployment(deploymentId); // 使用部署ID删除流程定义 不带级联的删除，只能删除没有启动的流程，如果流程启动，就会抛出异常
			.deleteDeployment(deploymentId, true); // cascade 级联删除 不管流程是否启动，都删除
		log.info("删除流程定义成功，deploymentId = " + deploymentId);
	}
	
	/**
	 * 查看流程图
	 * @throws IOException 
	 * @throws FileNotFoundException 
	 */
	@Test
	public void viewImage() throws FileNotFoundException, IOException {
		// 将生成的图片放到本地文件夹下
		String deploymentId = "20001";
		String resourceName = null;
		// 获取资源名称
		List<String> deploymentResourceNames = processEngine.getRepositoryService() // 获取流程定义和流程部署相关的Service
			.getDeploymentResourceNames(deploymentId);
		
		if(null == deploymentResourceNames || deploymentResourceNames.size() == 0) {
			log.error("获取资源名称失败！");
			return ;
		}
		
		for(String name : deploymentResourceNames) {
			if(name.endsWith(".png")) {
				resourceName = name;
				break;
			}
		}
		
		// 获取图片的输入流
		InputStream in = processEngine.getRepositoryService() // 获取流程定义和流程部署相关的Service
			.getResourceAsStream(deploymentId, resourceName);
		
		File dir = new File("D:\\", resourceName);
		// 将出入流的图片写到D盘下
		FileCopyUtils.copy(in, new FileOutputStream(dir));
		
		log.info("生成图片成功！");
	}
	
	/**
	 * 查询最新版本的流程定义
	 * map集合的key为流程定义的key
	 * map集合的value为流程定义
	 * map集合的特点：当key相同的情况下，后一次的值将替换前一次的值
	 */
	@Test
	public void findLastVersionProcessDefinition() {
		List<ProcessDefinition> pdList = processEngine.getRepositoryService() // 获取流程定义和流程部署相关的Service
				.createProcessDefinitionQuery() // 创建流程定义查询 
				.orderByProcessDefinitionVersion().asc() // 使用流程定义的版本升序
				.list();
		
		if(null == pdList || pdList.size() == 0) {
			log.error("未查询到流程定义！");
			return;
		}
		
		/**
		 * map集合的key为流程定义的key
		 * map集合的value为流程定义
		 * map集合的特点：当key相同的情况下，后一次的值将替换前一次的值
		 */
		Map<String, ProcessDefinition> map = new LinkedHashMap<>();
		for(ProcessDefinition pd : pdList) {
			map.put(pd.getKey(), pd);
		}
		
		// 遍历流程的最新版本的map
		for(ProcessDefinition pd : map.values()) {
			log.info("流程定义ID=" + pd.getId() + "，流程定义的名称=" + pd.getName() + "，流程定义的key=" + pd.getKey() + "，流程定义的版本=" + pd.getVersion() 
			+ "，流程定义的bpmn文件=" + pd.getResourceName() + "，流程定义的png文件=" + pd.getDiagramResourceName() + "，部署对象ID=" + pd.getDeploymentId());
		}
				
	}
	
	/**
	 * 删除流程定义，删除key相同的所有不同版本的流程定义
	 * 
	 * 先使用流程定义的key查询流程定义，查询出所有的版本
	 */
	@Test
	public void deleteProcessDefinitionByKey() {
		RepositoryService repositoryService = processEngine.getRepositoryService();
		// 根据流程定义的key查询流程定义
		String processDefinitionKey = "LeaveProcess";
		List<ProcessDefinition> pdList = repositoryService// 
			.createProcessDefinitionQuery() // 
			.processDefinitionKey(processDefinitionKey) // 
			.list();
		
		if(null == pdList || pdList.size() == 0) {
			log.error("未查询到流程定义！");
			return;
		}
		
		// 遍历，获取流程部署Id
		for(ProcessDefinition pd : pdList) {
			String deploymentId = pd.getDeploymentId();
			repositoryService.deleteDeployment(deploymentId, true);
			log.info("删除流程定义，deploymentId = " + deploymentId);
		}
		
		log.info("删除成功！");
	}
}
